//-----------------------------------------------------------------------------
//
// CodeXS - Copyright(c) Businessware Architects cc
// Contact: Willem J Fourie
// E-mail : willemf@bware.biz
//
// This file is part of CodeXS.
//
// CodeXS is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License (GPL) as 
// published by the Free Software Foundation version 2 of the License.
//
// CodeXS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with CodeXS; if not, write to:
//
// Free Software Foundation, Inc.
// 59 Temple Place
// Suite 330
// Boston
// MA  02111-1307  USA
//
//-----------------------------------------------------------------------------

using System;
using System.Text;
using System.IO;
using System.Collections;
using System.Xml;
using System.Xml.XPath;
using System.Reflection;
using System.CodeDom;
using System.Diagnostics;
using CodeXS.Schema.CodeModifier;

namespace CodeXS.Schema
{
	public interface IDocumentation
	{
		string GetDocumentSection(string BaseSection, string SubSection);
		string DoDocumentSection(string BaseSection, string SubSection);
	}
	public class Documentation
	{
		private IDocumentation _documenter = null;
		private Assembly _assembly = null;
		public Assembly ExecutingAssembly
		{
			get { return _assembly; }
		}
		private XmlDocument _xmlDoc = null;
		private string LoadStringFromResource(Assembly Asm, 
			string ResourceName)
		{
			string resString = null;
			foreach(string resName in Asm.GetManifestResourceNames()) 
			{
				int findIndex = resName.IndexOf(ResourceName);
				if( (findIndex == -1) ||
					(resName.Substring(findIndex) != ResourceName) ) continue;
				StreamReader sr = null;
				try
				{
					Stream s = Asm.GetManifestResourceStream(resName);
					sr = new StreamReader(s);
					resString = sr.ReadToEnd();
				}
				catch (Exception e)
				{
					Diagnostics.Log.DefaultLog.LogException(e);
				}
				finally
				{
					if(sr != null) sr.Close();
				}
			}
			return resString;
		}
		private void Initialize()
		{
			object[] iDocumenter =
				CodeGenerator.LoadInterfacesFromAssembly(_assembly,
					typeof(IDocumentation).FullName);
			if(iDocumenter.Length > 0)
			{
				_documenter = iDocumenter[0] as IDocumentation;
			}
			string resource = this.LoadStringFromResource(_assembly,
				Defaults.DocumentationFileName);
			if(resource != null)
			{
				_xmlDoc = new XmlDocument();
				_xmlDoc.LoadXml(resource);
			}
		}
		public Documentation(Assembly Asm)
		{
			_assembly = Asm;
			this.Initialize();
		}
		public Documentation()
		{
			_assembly = Assembly.GetCallingAssembly();
			this.Initialize();
		}
		public string GetDocumentSection(string Path)
		{
			XmlNode node = null;
			try
			{
				// Split the last child
				string parent = Documentation.GetParentSection(Path);
				string name = Path.Replace(parent, "").Substring(1);
				string query = parent + Defaults.BlockTag + 
					"[attribute::name=\"" +	name + "\"]";
				node = _xmlDoc.SelectSingleNode(query);
			}
			catch (Exception e)
			{
				Diagnostics.Log.DefaultLog.LogException(e);
			}
			if(node != null)
			{
				return Documentation.RemoveBlockDelimiters(node.InnerXml);
			}
			return null;
		}
		static public string RemoveBlockDelimiters(string Block)
		{
			while(Block.IndexOfAny(new char[]{'\n','\t'}) == 0) 
				Block = Block.Substring(1);
			if(Block[0] == '\r')
				Block = Block.Substring(1);
			while(Block.LastIndexOfAny(new char[]{'\n','\t'}) == 
					Block.Length - 1)
				Block = Block.Substring(0, Block.Length - 1);
			if(Block[Block.Length - 1] == '\r')
				Block = Block.Substring(0, Block.Length - 1);
			return Block;
		}
		static public string ConformLineDelimiters(string Block)
		{
			string[] sectionEnds =
			{
				"<summary>",
				"</summary>",
				"<para>",
				"</para>",
				"<param ",
				"</param>",
				"<remarks>",
				"</remarks>",
				"<returns>",
				"</returns>",
			};
			Block = Block.Replace("\r\n", "\r");
			Block = Block.Replace("\n\r", "\r");
			Block = Block.Replace("\n", "\r");
			string block = "";
			foreach(string sectionEnd in sectionEnds)
			{
				block = Block;
				int charsAdded = 0;
				string s = sectionEnd;
				int i = -1;
				while( (i = Block.IndexOf(s, i + 1)) >= 0)
				{
					if( ! s.EndsWith(">"))
					{
						int ie = Block.IndexOf(">", i + 1);
						s = Block.Substring(i, ie - i + 1);
					}
					if( (i < Block.Length - 1 - s.Length) &&
						(Block[i + s.Length] != '\r') )
					{
						block = block.Substring(0, i + charsAdded) +
							s + "\r" +
							Block.Substring(i + s.Length);
						++charsAdded;
					}
				}
				Block = block;
			}
			return block;
		}
		static public string ConformContent(string Block)
		{
			string[,] csReplace = 
			{
				{ "<param name=\"@string\">", "<param name=\"string\">"},
			};
			string[,] vbReplace = 
			{
				{ "<param name=\"[string]\">", "<param name=\"string\">"},
				{ "<param name=\"[date]\">", "<param name=\"date\">"},
			};
			string[,] replace = 
				(CodeGenerator.CodeLanguage == CodeGenerator.Language.CS) ?
					csReplace : vbReplace;
			for(int i = 0; i < replace.GetLength(0); i++)
			{
				Block = Block.Replace(replace[i,0], replace[i,1]);
			}
			return Block;
		}
		static public string[] SplitBlock(string Block)
		{
			Block = Documentation.ConformLineDelimiters(Block);
			return Block.Split(new char[]{'\r'});
		}
		static public string AddLineToBlock(string Block, string Line)
		{
			Block += "\r" + Line;
			return Block;
		}
		private CodeCommentStatement[] CreateDocumentation(
			CodeGenerator CodeGenerator,
			ReplaceableParameter Replace,
			string BaseName, string SectionName)
		{
			CodeCommentStatement[] commentBlock = null;
			string section = BaseName +
				(string)((SectionName != null) ?  @"/" + SectionName : "");
			string content = null;
			if(_documenter != null)
			{
				section = _documenter.GetDocumentSection(BaseName, section);
				content = 
					_documenter.GetDocumentSection(BaseName, section);
			}
			if(content != null)
			{
				commentBlock = 
					Documentation.CreateCommentBlock(content, true);
			}
			else
			{
				commentBlock = Documentation.CreateCommentBlock(
					this.ExecutingAssembly,
					Defaults.DocumentationFileName, section,
					Replace, true);
			}
			return commentBlock;
		}
		public enum MemberUserData
		{
			Declarer,
			Namespace,
		}
		public void CreateDocumentation(
			CodeGenerator CodeGenerator,
			ReplaceableParameter Replace,
			CodeTypeMember Member,
			string BaseName,
			bool UseMemberSections,
			bool CommentMembers)
		{
			if( (Member is CodeTypeDeclaration) && CommentMembers)
			{
				CodeTypeDeclaration decl = (CodeTypeDeclaration)Member;
				foreach(CodeTypeMember member in decl.Members)
				{
					member.UserData[MemberUserData.Declarer.ToString()] = decl;
					member.UserData[MemberUserData.Namespace.ToString()] =
						CodeGenerator.CodeNamespace;
					this.CreateDocumentation(CodeGenerator, 
						Replace, member, BaseName, UseMemberSections, CommentMembers);
				}
			}
			Member.Comments.Clear();
			CodeCommentStatement[] commentBlock = null;
			string section = null;
			if(UseMemberSections)
			{
				for(int i = 0; i < 3; i++)
				{
					section = this.GetMemberDescriptor(Member,
						(MemberInfo)i);
					commentBlock = 
						this.CreateDocumentation(CodeGenerator, 
						Replace, BaseName, section);
					if(commentBlock != null) break;
				}
			}
			else
			{
				commentBlock = 
					this.CreateDocumentation(CodeGenerator, 
					Replace, BaseName, section);
			}
			if(commentBlock == null)
			{
				if(section == null) section = BaseName;
				section = Documentation.DefaultDocumentation(section);
				commentBlock = Documentation.CreateCommentBlock(
					section, true);
			}
			if(commentBlock != null)
			{
				Member.Comments.AddRange(commentBlock);
			}
		}
		private string GetMemberNamespaceName(CodeTypeMember Member)
		{
			object ns = Member.UserData[MemberUserData.Namespace.ToString()];
			return (ns == null) ? "" : ((CodeNamespace)ns).Name;
		}
		private string GetMemberClassName(CodeTypeMember Member)
		{
			object cls = Member.UserData[MemberUserData.Declarer.ToString()];
			return (cls == null) ? "" : ((CodeTypeDeclaration)cls).Name;
		}
		public enum MemberInfo
		{
			Name = 0,
			FullName = 1,
			FullDecl = 2,
		}
		private string GetMemberName(CodeTypeMember Member)
		{
			string name = Member.Name;
			string clsName = this.GetMemberClassName(Member);
			if(clsName == "")
			{
				if(Member is CodeTypeDeclaration) name = "class";
			}
			if(name == "")
			{
				if(Member is CodeConstructor)
				{
					name = clsName + ".ctor";
				}
			}
			return name;
		}
		private string GetMemberFullName(CodeTypeMember Member)
		{
			string name = this.GetMemberName(Member);
			string clsName = this.GetMemberClassName(Member);
			if(clsName.Length > 0) clsName += @".";
			if(Member is CodeMemberField)
			{
				CodeMemberField field = (CodeMemberField)Member;
				name =  clsName + name;
			}
			if(Member is CodeMemberProperty)
			{
				CodeMemberProperty property = (CodeMemberProperty)Member;
				name = clsName + name;
			}
			if(Member is CodeMemberMethod)
			{
				CodeMemberMethod method = (CodeMemberMethod)Member;
				name = clsName + name;
			}
			if(Member is CodeMemberEvent)
			{
				CodeMemberEvent ev = (CodeMemberEvent)Member;
				name = clsName + name;
			}
			return name;
		}
		private string GetMemberFullDecl(CodeTypeMember Member)
		{
			string name = this.GetMemberFullName(Member);
			CodeNamespace ns = (CodeNamespace)
				Member.UserData[MemberUserData.Namespace.ToString()];
			CodeTypeDeclaration decl = (CodeTypeDeclaration)
				Member.UserData[MemberUserData.Declarer.ToString()];
			if(Member is CodeMemberProperty)
			{
				CodeMemberProperty property = (CodeMemberProperty)Member;
				string referenced = "";
				if(property.PrivateImplementationType != null)
				{
					referenced = property.PrivateImplementationType.BaseType;
				}
				referenced = referenced.Replace(@".", @"#");
				name = referenced + @"#" + name;
			}
			if(Member is CodeMemberMethod)
			{
				CodeMemberMethod method = (CodeMemberMethod)Member;
				name += "(";
				foreach(CodeParameterDeclarationExpression parm in 
					method.Parameters)
				{
					name += parm.Type.BaseType + ",";
				}
				if(name.LastIndexOf(",") == name.Length - 1)
					name = name.Substring(0, name.Length - 1);
				name += ")";
			}
			return name;
		}
		public string GetMemberDescriptor(CodeTypeMember Member, 
			MemberInfo Info)
		{
			string info = null;
			switch(Info)
			{
				case MemberInfo.Name:
					info = this.GetMemberName(Member);
					break;
				case MemberInfo.FullName:
					info = this.GetMemberFullName(Member);
					break;
				case MemberInfo.FullDecl:
					info = this.GetMemberFullDecl(Member);
					break;
			}
			return info;
		}
		static public CodeCommentStatement[] CreateCommentBlock(string Content,
			bool DocComment)
		{
			ArrayList commentArray = new ArrayList();
			Content = Documentation.ConformContent(Content);
			Content = Documentation.ConformLineDelimiters(Content);
			string[] comments = Content.Split(new char[]{'\r'});
			foreach(string comment in comments)
			{
				commentArray.Add(new CodeCommentStatement(comment, DocComment));
			}
			return (CodeCommentStatement[])
				commentArray.ToArray(typeof(CodeCommentStatement));
		}
		static string GetParentSection(string SectionName)
		{
			int idx = SectionName.LastIndexOf(@"/");
			if(idx > 0) SectionName = SectionName.Substring(0, idx);
			else SectionName = "";
			return SectionName;
		}
		static public CodeCommentStatement[] CreateCommentBlock(
			Assembly Asm, string ResourceName, string SectionName,
			ReplaceableParameter Replace, bool DocComment)
		{
			Documentation docObject = new Documentation(Asm);
			string section = null;
			section = docObject.GetDocumentSection(
				Defaults.DocumentationTag + SectionName);
			CodeCommentStatement[] content = null;
			if(section != null)
			{
				if(Replace != null) section = Replace.Replace(section);
				content = Documentation.CreateCommentBlock(section, 
					DocComment);
			}
			return content;
		}
		static public string DefaultDocumentation(string SectionName)
		{
			return CreateSectionOpenTag(Section.Summary) + 
				"\rTODO: Documentation for '" + SectionName + "'.\r" +
				CreateSectionCloseTag(Section.Summary);
		}
		// CHANGE: 2006-06-26:	  Version 0.57 Beta
		// Added standard documentation functions.
		public enum Section { Summary, Remarks, Para }
		static public string CreateSectionOpenTag(Section section)
		{
			return "<" + section.ToString().ToLower() + ">";
		}
		static public string CreateSectionCloseTag(Section section)
		{
			return "</" + section.ToString().ToLower() + ">";
		}
		static public string CreateEmptySectionTag(Section section)
		{
			return "<" + section.ToString().ToLower() + "/>";
		}
		static public string CreateDocumentationBlock(
			Section section, string documentation)
		{
			return CreateSectionOpenTag(section) +
				documentation +
				CreateSectionCloseTag(section);
		}
		public static string MergeCommentStatements(
			CodeCommentStatement[] codeCommentStatements)
		{
			StringBuilder merged = new StringBuilder();
			foreach (CodeCommentStatement statement in codeCommentStatements)
			{
				if (merged.Length == 0)
				{
					merged.Append(statement.Comment.Text);
				}
				else
				{
					merged.Append("\r");
					merged.Append(statement.Comment.Text);
				}
			}
			return merged.ToString();
		}
		public static string MergeCommentStatements(
			CodeCommentStatementCollection codeCommentStatements)
		{
			string merged = string.Empty;
			foreach (CodeCommentStatement statement in codeCommentStatements)
			{
				if (merged == string.Empty)
				{
					merged = statement.Comment.Text;
				}
				else
				{
					merged += "\r" + statement.Comment.Text;
				}
			}
			return merged;
		}
		private void ChangeCommentSectionTag(CodeTypeMember member,
			Documentation.Section fromSection,
			Documentation.Section toSection)
		{
			string merged = MergeCommentStatements(member.Comments);
			merged.Replace(CreateSectionOpenTag(fromSection),
				CreateSectionOpenTag(toSection));
			merged.Replace(CreateSectionCloseTag(fromSection),
				CreateSectionCloseTag(toSection));
			member.Comments.AddRange(CreateCommentBlock(merged, true));
		}
		public static string GetDocumentationSection(CodeTypeMember member,
			Section section)
		{
			string merged = MergeCommentStatements(member.Comments);
			string tag = CreateSectionOpenTag(section);
			int index = merged.IndexOf(tag);
			if (index >= 0)
			{
				merged = merged.Substring(index + tag.Length);
				tag = CreateSectionCloseTag(section);
				index = merged.IndexOf(tag);
				if (index >= 0)
				{
					merged = merged.Substring(0, index);
				}
			}
			else
			{
				merged = string.Empty;
			}
			return merged;
		}
		public static void ReplaceDocumentationSection(CodeTypeMember member,
			Section section, string documentation)
		{
			string merged = MergeCommentStatements(member.Comments);
			string sectionDoc = GetDocumentationSection(member, section);
			documentation = documentation.Trim(new char[] { '\r', '\n' });
			documentation = "\r" + documentation + "\r";
			if (sectionDoc != string.Empty)
			{
				merged = merged.Replace(
					Documentation.CreateDocumentationBlock(section, sectionDoc),
					Documentation.CreateDocumentationBlock(section, documentation));
			}
			else
			{
				merged = Documentation.CreateDocumentationBlock(section, documentation);
			}
			member.Comments.Clear();
			member.Comments.AddRange(
				Documentation.CreateCommentBlock(merged, true));
		}
		public static void InsertDocumentationSection(CodeTypeMember member,
			int insertIndex, string documentation)
		{
			CodeCommentStatement[] docComments =
				Documentation.CreateCommentBlock(documentation, true);
			for (int i = docComments.Length; i >= 0; i--)
			{
				member.Comments.Insert(insertIndex, docComments[i]);
			}
		}
		public static void AddDocumentationSection(CodeTypeMember member,
			string documentation)
		{
			CodeCommentStatement[] docComments =
				Documentation.CreateCommentBlock(documentation, true);
			member.Comments.AddRange(docComments);
		}
	}
}
